﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Security.AccessControl;
using System.Security.Permissions;
using System.Security.Principal;

namespace FileMatcherLib
{
    public class FileScanner
    {
        #region Nested types

        class FileComparerBySize : IComparer<FileInfo>
        {
            public int Compare(FileInfo x, FileInfo y)
            {
                var comp = x.Length.CompareTo(y.Length);
                if (comp != 0)
                {
                    return comp;
                }
                comp = String.Compare(x.Name, y.Name, StringComparison.Ordinal);
                if (comp != 0)
                {
                    return comp;
                }
                comp = String.Compare(x.FullName, y.FullName, StringComparison.Ordinal);
                return comp;
            }

            public static readonly FileComparerBySize Instance = new FileComparerBySize();
        }

        #endregion

        #region Constructors

        public FileScanner(string startingPath)
            : this(new DirectoryInfo(startingPath))
        {
        }

        public FileScanner(DirectoryInfo startingDir)
        {
            _startingDir = startingDir;
        }

        #endregion

        #region Methods

        public List<FileInfo> FilesBySize
        {
            get
            {
                if (!_filesBySizeUpdated)
                {
                    PopulateFileBySizeList();
                }
                return _filesBySize;
            }
        }

        private bool Contains(FileSystemRights right, FileSystemAccessRule rule)
        {
            return (((int)right & (int)rule.FileSystemRights) == (int)right);
        }  

        private bool CheckIfHavingAccess(DirectoryInfo d)
        {
            var ac = d.GetAccessControl();
            var acc = ac.GetAccessRules(true, true, typeof (NTAccount));
            var currIdentityRef = WindowsIdentity.GetCurrent().Name;

            var userAllowedAccess = false;
            foreach (FileSystemAccessRule rule in acc)
            {
                if (rule.IdentityReference.Value.Equals(currIdentityRef, StringComparison.CurrentCultureIgnoreCase))
                {
                    userAllowedAccess = ((rule.FileSystemRights & FileSystemRights.Read) > 0);
                    break;
                }
            }

            if (!userAllowedAccess)
            {
                return false;
            }
#if false
            acc = ac.GetAccessRules(true, true, typeof(SecurityIdentifier));
            foreach (FileSystemAccessRule rule in acc)
            {
                if (WindowsIdentity.GetCurrent().User.Equals(rule.IdentityReference))
                {
                    if (AccessControlType.Deny.Equals(rule.AccessControlType))
                    {
                        if (Contains(FileSystemRights.Read, rule))
                        {
                            // deny read
                            return false;
                        }
                    }
                }
            }
#endif
            var permissionSet = new PermissionSet(PermissionState.None); 
            var readPermission = new FileIOPermission(FileIOPermissionAccess.Read, d.FullName);
            permissionSet.AddPermission(readPermission);
            var permitted = permissionSet.IsSubsetOf(AppDomain.CurrentDomain.PermissionSet);
            if (!permitted) return false;

            permitted = true;
            try
            {
                d.GetDirectories();
            }
            catch(Exception)
            {
                permitted =false;
            }
            return permitted;
        }

        IEnumerable<DirectoryInfo> TryGetDirectories(DirectoryInfo d)
        {
            try
            {
                return d.GetDirectories();
            }
            catch (Exception)
            {
                return new DirectoryInfo[0];
            }
        }

        IEnumerable<FileInfo> TryGetFiles(DirectoryInfo d)
        {
            try
            {
                return d.GetFiles();
            }
            catch (Exception)
            {
                return new FileInfo[0];
            }
        }
 
        protected IEnumerable<FileInfo> GetFileEnumerator()
        {
            var qd = new Queue<DirectoryInfo>();
            qd.Enqueue(_startingDir);
            while (qd.Count > 0)
            {
                var d = qd.Dequeue();
                var subds = TryGetDirectories(d); 
                foreach (var subd in subds)
                {
                    qd.Enqueue(subd);
                }
                var fs = TryGetFiles(d);
                foreach (var f in fs)
                {
                    yield return f;
                }
            }
        }

        protected void PopulateFileBySizeList()
        {
            var files = GetFileEnumerator();
            foreach (var f in files)
            {
                var iPos = _filesBySize.BinarySearch(f, FileComparerBySize.Instance);
                if (iPos >= 0) continue;    // already exists (actually impossible)
                iPos = -iPos - 1;
                _filesBySize.Insert(iPos, f);
            }
            _filesBySizeUpdated = true;
        }

        #endregion

        #region Fields

        private readonly DirectoryInfo _startingDir;
        private readonly List<FileInfo> _filesBySize = new List<FileInfo>();
        private bool _filesBySizeUpdated;

        #endregion
    }
}
